﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Collections;

namespace NetCommand 
{
    public delegate object getFieldValueDelegate(byte[] bValue );
    public delegate byte[] getBytesFromFieldDelegate(object value);
    public class CommandTranslator
    {
        static List<byte> lastByte = new List<byte>();

        private Dictionary<Type, int> typeSizeMap = new Dictionary<Type, int>();
        private Dictionary<Type, getFieldValueDelegate> getValueMap = new Dictionary<Type, getFieldValueDelegate>();
        private Dictionary<Type, getBytesFromFieldDelegate> getBytesMap = new Dictionary<Type, getBytesFromFieldDelegate>();

        private CommandTranslator()
        {
            typeSizeMap[typeof(int)]    = sizeof(int);
            typeSizeMap[typeof(uint)]   = sizeof(uint); 
            typeSizeMap[typeof(short)]  = sizeof(short);
            typeSizeMap[typeof(ushort)] = sizeof(ushort);
            typeSizeMap[typeof(byte)]   = sizeof(byte);
            typeSizeMap[typeof(string)] = sizeof(byte);     // 用ASCII编码 string是由byte组成  在Unicode下char的size是2 
            typeSizeMap[typeof(sbyte)]  = sizeof(sbyte);
            typeSizeMap[typeof(double)] = sizeof(double);
            typeSizeMap[typeof(float)]  = sizeof(float);

            getValueMap[typeof(int)]        = getInt32Value;
            getValueMap[typeof(uint)]       = getUInt32Value;
            getValueMap[typeof(short)]      = getInt16Value;
            getValueMap[typeof(ushort)]     = getUInt16Value;
            getValueMap[typeof(byte[])]     = getBytesValue;
            getValueMap[typeof(string)]     = getStringValue;
            getValueMap[typeof(UInt16[])]   = getUint16ArrayValue;

            getBytesMap[typeof(int)]        = getDataInt32Value;
            getBytesMap[typeof(uint)]       = getDataUInt32Value;
            getBytesMap[typeof(short)]      = getDataInt16Value;
            getBytesMap[typeof(ushort)]     = getDataInt16Value;
            getBytesMap[typeof(byte[])]     = getDataBytesValue;
            getBytesMap[typeof(string)]     = getDataStringValue;
            getBytesMap[typeof(UInt16[])]   = getDataUInt16ArrayValue;
        }

        private static CommandTranslator _instance;
        public static CommandTranslator Instance
        {
            get
            {
                if (_instance == null)
                {
                    _instance = new CommandTranslator();
                }
                return _instance;
            }
        }

        public int CHECK_CMD_FRONT_CODE
        {
            get;
            set;
        }

        private byte[] getPointBytes(byte[] value, int length, int offset)
        {
            byte[] bRet = new byte[length];
            for (int i = 0; i < length; i++)
            {
                bRet[i] = value[i + offset];
            }
            return bRet;
        }
        
        private object getInt16Value(byte[] bValue)
        {
            return EncodingTools.bytesToShort(bValue, 0);
        }

        private object getUInt16Value(byte[] bValue)
        {
            return EncodingTools.bytesToUShort(bValue, 0);
        }

        private object getUint16ArrayValue(byte[] bValue)
        {
            int length = bValue.Length / 2;
            UInt16[] nValue = new UInt16[length];
            for (int i = 0; i < length; i++)
            {
                nValue[i] = EncodingTools.bytesToUShort(new byte[] { bValue[2 * i], bValue[2 * i + 1] }, 0);
            }
            return nValue;
        }
        
        private object getInt32Value(byte[] bValue)
        {
            return EncodingTools.bytesToInt(bValue, 0);
        }

        private object getUInt32Value(byte[] bValue)
        {
            return EncodingTools.bytesToUInt(bValue, 0);
        }

        private object getBytesValue(byte[] bValue)
        {
            return bValue;
        }

        private object getStringValue(byte[] bValue)
        {
            return Encoding.ASCII.GetString(bValue);
        }

        private void setListValue(FieldInfo field, object src, byte[] bValue)
        {
            Type listContentType = getListContentType(field.FieldType);
            int nSize = getListContentSize(field.FieldType, bValue);
            int nCount = bValue.Length / nSize;
            List<byte[]> nCmdValue = splitByte(bValue, nCount);
            if (nCmdValue == null)
            {
                return;
            }
            bool isNormalType = getValueMap.ContainsKey(listContentType);
            for (int i = 0; i < nCount; i++)
            {
                IList list = field.GetValue(src) as IList;
                if (isNormalType)
                {
                    object item = getValueMap[listContentType](nCmdValue[i]);
                    list.Add(item);
                }
                else
                {
                    INetCommand iCmd = System.Activator.CreateInstance(listContentType) as INetCommand;
                    setCmdValue(nCmdValue[i], iCmd);
                    list.Add(iCmd);
                }
            }
        }

        private void setClassInistanceValue(FieldInfo field, object src, byte[] bValue)
        {
            INetCommand iCmd = System.Activator.CreateInstance(field.FieldType) as INetCommand;
            if (iCmd == null)
            {
                return;
            }
            setCmdValue(bValue, iCmd);
            field.SetValue(src, iCmd);
        }

        private void setFieldValue(FieldInfo field, INetCommand cmd, byte[] value, int size)
        {
            byte[] bValue = getPointBytes(value, size, 0);

            if (getValueMap.ContainsKey(field.FieldType))
            {
                object v = getValueMap[field.FieldType](bValue);
                field.SetValue(cmd, v);
            }
            else if (field.FieldType.Name == "List`1")
            {
                setListValue(field, cmd, bValue);
            }
            else if (field.FieldType == typeof(byte))
            {
                field.SetValue(cmd, bValue[0]);
            }
            else
            {
                setClassInistanceValue(field, cmd, bValue);
            }
        }
        
        private List<byte[]> splitByte(byte[] bValue, int size)
        {
            int nCount = bValue.Length / size;
            if (nCount < 1)
            {
                return null;
            }
            List<byte[]> lRet = new List<byte[]>();
            int start = 0;
            for (int i = 0; i< size; i++)
            {
                byte[] bV = new byte[nCount];
                bV = EncodingTools.CopyBytes(bValue, start, nCount);
                start += nCount;
                lRet.Add(bV);
            }
            return lRet;
        }

        private NetCommandHeader getMessageHeader(byte[] bValue)
        {
            NetCommandHeader header = new NetCommandHeader();
            if (bValue.Length == 1)
            {
                return header;
            }

            header.CMD_FRONT_ID = bValue[NetCommon.Index_FRONT_ID];
            header.CMD_AREA_ID = EncodingTools.bytesToUShort (new byte[] { bValue[NetCommon.Index_AREA_ID_1], bValue[NetCommon.Index_AREA_ID_2]}, 0);
            header.CMD_DEVICE_ID = bValue[NetCommon.Index_DEVICE_ID];
            header.CMD_R_W = bValue[NetCommon.S2D_READ];
            header.CMD_LEN = bValue[NetCommon.Index_DATA_LEN];
            header.CMD_ID = EncodingTools.bytesToUShort(new byte[] { bValue[NetCommon.Index_CMD_ID_1], bValue[NetCommon.Index_CMD_ID_2] }, 0);
            return header;
        }

        private byte[] getMessageData(byte[] bMessage, byte length)
        {
            int messageDataStartIndex = NetCommandHeader.getSize();
            byte[] bRet = new byte[length];
            if (messageDataStartIndex + length > bMessage.Length)
            {
                return bRet;
            }
            for (int i = 0; i < length; i++)
            {
                bRet[i] = bMessage[messageDataStartIndex + i];
            }
            return bRet;
        }

        private bool checkCRC(byte[] bMessage, UInt16 check_crc)
        {
            bool bRet = false;
            byte[] checkValue = EncodingTools.CopyBytes(bMessage, bMessage.Length - 2); ; // CRC 校验 除CRC之外的数据
            
            UInt16 crc = CRC.crc16(checkValue);
            if (crc == check_crc)
            {
                bRet = true;
            }
            return bRet;
        }

        private void setCmdValue(byte[] bData, INetCommand cmd)
        {
            int startIndex = 0;
            FieldInfo[] fields = cmd.GetType().GetFields();
            for (int i = 0; i < fields.Length; i++ )
            {
                object[] fieldattrs = fields[i].GetCustomAttributes(false);

                if (fieldattrs.Length > 0)
                {
                    SerializeFieldAttribute attr = (SerializeFieldAttribute)fieldattrs[0];
                    if (attr == null)
                    {
                        continue;
                    }

                    // [字长] [值域]
                    int fieldLength = 1;
                    // List需要在前面指定其长度
                    if (fields[i].FieldType.Name == "List`1")
                    {
                        fieldLength = bData[startIndex];
                        startIndex++;
                    }
                    else if (fields[i].FieldType == typeof(string))
                    {
                        fieldLength = bData[startIndex];
                        startIndex++;
                    }

                    byte[] data = EncodingTools.CopyBytes(bData, startIndex, bData.Length - startIndex);
                    fieldLength *= getFieldSize(fields[i].GetValue(cmd),data);
                    setFieldValue(fields[i], cmd, data, fieldLength);
                    startIndex += fieldLength;
                }
            }
        }
        
        private int getFieldSize(object netObj, byte[] bData)
        {
            int size = 0;

            Type fieldType = netObj.GetType();
            if (typeSizeMap.ContainsKey(fieldType))
            {
                size = typeSizeMap[fieldType];
            }
            else if (fieldType.Name == "List`1")
            {
                size = getListContentSize(fieldType, bData);
            }
            else if(isArray(netObj, fieldType, ref size))
            { }
            else
            {
                size = getNetStructSize(fieldType, bData);
            }
            return size;
        }

        private bool isArray(object fieldValue, Type fieldType, ref int size)
        {
            bool isArray = false;
            if (fieldType == typeof(Int16[]))
            {
                size = sizeof(Int16) * ((Int16[])fieldValue).Length;
                isArray = true;
            }
            else if (fieldType == typeof(UInt16[]))
            {
                size = sizeof(UInt16) * ((UInt16[])fieldValue).Length;
                isArray = true;
            }
            else if (fieldType == typeof(Int32[]))
            {
                size = sizeof(Int32) * ((Int32[])fieldValue).Length;
                isArray = true;
            }
            else if (fieldType == typeof(UInt32[]))
            {
                size = sizeof(UInt32) * ((UInt32[])fieldValue).Length;
                isArray = true;
            }
            else if (fieldType == typeof(SByte[]))
            {
                size = sizeof(SByte) * ((SByte[])fieldValue).Length;
                isArray = true;
            }
            return isArray;
        }

        private int getNetStructSize(Type fieldType,byte[] bData)
        {
            int size = 0;
            INetCommand stCmd = System.Activator.CreateInstance(fieldType) as INetCommand;
            if (stCmd == null)
            {
                throw new Exception("Not impliment");
            }
            FieldInfo[] fieldInfos = fieldType.GetFields();
            int startIndex = 0;
            for (int i = 0; i < fieldInfos.Length; i++)
            {
                object[] fieldattrs = fieldInfos[i].GetCustomAttributes(false);
                if (fieldattrs.Length > 0)
                {
                    SerializeFieldAttribute attr = (SerializeFieldAttribute)fieldattrs[0];
                    if (attr == null)
                    {
                        continue;
                    }
                    // [字长] [值域]
                    int fieldLength = 1;
                    // List需要在前面指定其长度
                    if (fieldInfos[i].FieldType.Name == "List`1")
                    {
                        fieldLength = bData[startIndex];
                        startIndex++;
                        size++;
                    }
                    else if (fieldInfos[i].FieldType == typeof(string))
                    {
                        fieldLength = bData[startIndex];
                        size++;
                    }

                    bData = EncodingTools.CopyBytes(bData, startIndex, bData.Length - startIndex);

                    fieldLength *= getFieldSize(fieldInfos[i].GetValue(stCmd), bData);
                    startIndex += fieldLength;
                    size += fieldLength;
                }
            }
            return size;
        }
        /*
         * 注意 这个List 里面不能放数组， 放了数组之后就无法初始化了
         */
        private int getListContentSize(Type fieldType,byte[] bData)
        {
            Type tp = getListContentType(fieldType);
            if (tp == null)
            {
                return 0;
            }
            return getFieldSize(System.Activator.CreateInstance(tp), bData);
        }

        private Type getListContentType(Type listType)
        {
            string fullName = listType.FullName;
            int pos = fullName.IndexOf("[[");
            if (pos == -1)
            {
                return null;
            }
            fullName = fullName.Substring(pos + 2);
            string typeName = fullName.Split(',')[0];

            Type tp = Type.GetType(typeName);

            return tp;
        }
        
        private byte[] fillCommandData(INetCommand cmd)
        {
            List<byte> lbRet = new List<byte>();
            FieldInfo[] fields = cmd.GetType().GetFields();
            for (int i = 0; i < fields.Length; i++)
            {
                object[] fieldattrs = fields[i].GetCustomAttributes(false);

                if (fieldattrs.Length > 0)
                {
                    SerializeFieldAttribute attr = (SerializeFieldAttribute)fieldattrs[0];
                    if (attr == null)
                    {
                        continue;
                    }
                    lbRet.AddRange(getDataBytesValue(cmd, fields[i]));
                }                
                    
            }
            return lbRet.ToArray();
        }

        private byte[] getDataBytesValue(INetCommand cmd, FieldInfo field)
        {
            byte[] bRet = null;
            if (getBytesMap.ContainsKey(field.FieldType))
            {
                bRet = getBytesMap[field.FieldType](field.GetValue(cmd));
            }
            else if (field.FieldType.Name == "List`1")
            {
                IList list = field.GetValue(cmd) as IList;
                bRet = getListBytesValue(list);
            }
            else if (field.FieldType == typeof(byte))
            {
                byte bValue = (byte)field.GetValue(cmd);
                bRet = new byte[1] { bValue };
            }
            else
            {
                bRet = fillCommandData(cmd);
            }
            
            return bRet;
        }
        
        private byte[] getListBytesValue(IList list)
        {
            List<byte> lbRet = new List<byte>();
            if (list.Count == 0)
            {
                return null;
            }
            Type contentType = list[0].GetType();
            bool isNormalType = getBytesMap.ContainsKey(contentType);
            lbRet.Add((byte)list.Count);
            for (int i = 0; i < list.Count; i++)
            {
                if (isNormalType)
                {
                    lbRet.AddRange(getBytesMap[contentType](list[i]));
                }
                else if(contentType.Name == "List`1")
                {
                    IList lst = list[i] as IList;
                    lbRet.AddRange(getListBytesValue(lst));
                }
                else
                {
                    lbRet.AddRange(fillCommandData((INetCommand)list[i]));
                }
            }
            return lbRet.ToArray();
        }

        private byte[] getDataUInt16ArrayValue(object objValue)
        {
            UInt16[] nValue = (UInt16[])objValue;
            byte[] bRet = new byte[nValue.Length * 2];
            for (int i = 0; i < nValue.Length ; i++)
            {
                byte[] bValue = EncodingTools.ushortToBytes(nValue[i]);

                bRet[2 * i] = bValue[0];
                bRet[2 * i + 1] = bValue[1];  
            }
            return bRet;
        }
        
        private byte[] getDataInt32Value(object objectValue)
        {
            return EncodingTools.intToBytes((int)objectValue);
        }

        private byte[] getDataUInt32Value(object objectValue)
        {
            return EncodingTools.uintToBytes((uint)objectValue);
        }

        private byte[] getDataBytesValue(object objectValue)
        {
            return (Byte[])objectValue;           
        }
        
        private byte[] getDataInt16Value(object objectValue)
        {
            return EncodingTools.shortToBytes((Int16)objectValue);
        }

        private byte[] getDataStringValue(object value)
        {
            byte[] bStr = Encoding.ASCII.GetBytes((string)value);
            List<byte> bRet = new List<byte>();
            bRet.Add((byte)bStr.Length);
            bRet.AddRange(bStr);
            return bRet.ToArray();
        }

        /// <summary>
        /// 解码消息
        /// </summary>
        /// * 命令格式
        /// * 命令前导码 （1个byte） + 站点区域邮编（3个byte） + 设备编号（1个byte） + 读写标识（1个byte） + 命令长度（1个byte） + 命令地址（两个byte） + 数据域（命令长度） + CRC高字节（1个byte） + CRC低字节（1个byte）
        /// * 命令头有9个字节
        /// <param name="bMessage">来自网络的字节流</param>
        /// <param name="errorCode">错误码</param>
        /// <returns>解码后的消息实例 如果为null 需要查看errorCode</returns>
        public NetCommand DecodeMessage(ref byte[] bMessage, ref UInt16 errorCode)
        {            
            if (bMessage == null)
            {
                errorCode = NetCode.CMD_DATA_NULL;
                return null;
            }
            if (bMessage.Length == 0)
            {
                errorCode = NetCode.CMD_TYPE_ERROR;
                return null;
            }

            if (bMessage[0] != NetCommon.D2S_HEADER_CODE)
            {
                // 消息缓冲区内的开头字符不是 消息前导码  并且上一次的消息 分包了
                if (lastByte.Count != 0)
                {
                    lastByte.AddRange(bMessage);
                    bMessage = lastByte.ToArray();
                    lastByte.Clear();
                }
                else
                {
                    errorCode = NetCode.CMD_FRONT_CODE_ERROR;
                    return null;
                }
            }
            if (bMessage.Length < NetCommandHeader.getSize())
            {
                // 有消息头但是 消息长度小于消息头的长度，分包了
                lastByte.AddRange(bMessage);
                errorCode = NetCode.CMD_SUBPACKAGE;
                return null;
            }

            NetCommandHeader cmdHeader = getMessageHeader(bMessage);

            // 消息长度如果大于当msg的长度 也分包了 等待下一条msg
            if (cmdHeader.CMD_LEN > bMessage.Length)
            {
                lastByte.AddRange(bMessage);
                errorCode = NetCode.CMD_SUBPACKAGE;
                return null;
            }

            int msgLength = cmdHeader.CMD_LEN;// 消息头长度 + 消息体长度 + CRC校验长度
            byte[] bMsgValue = EncodingTools.CopyBytes(bMessage, msgLength);
            byte headerSize = (byte)NetCommandHeader.getSize();
            byte crcLength = (byte)NetCommon.CRCLength;
            byte dataLength = (byte)(cmdHeader.CMD_LEN - headerSize - crcLength);
            byte[] bData = getMessageData(bMsgValue, dataLength);
            Type _type = NetCommandFactory.GetCommandType(cmdHeader.CMD_ID);
            if (_type == null)
            {// 命令ID错误
                errorCode = NetCode.CMD_TYPE_ERROR;
                return null;
            }
            NetCommand cmd = (NetCommand)System.Activator.CreateInstance(_type);
            cmd._CRC = EncodingTools.bytesToUShort(new byte[] { bMsgValue[msgLength - 2], bMsgValue[msgLength - 1] }, 0);
            cmd.SpareByte = EncodingTools.bytesToUShort(new byte[] { bMsgValue[msgLength - 4], bMsgValue[msgLength - 3] }, 0);

            if (!checkCRC(bMsgValue, cmd._CRC))
            {// crc 校验失败
                errorCode = NetCode.CRC_ERROR;
                return null;
            }
            errorCode = NetCode.NO_ERROR;
            cmd.Header = cmdHeader;
            cmd.R_W = bMessage[4];
            if (cmd.R_W == NetCommon.S2D_WRITE)
            {
                cmd.ErrorCode = bMessage[8];
            }
            else
            {
                setCmdValue(bData, cmd);
            }
            // 处理粘包
            bMessage = EncodingTools.CopyBytes(bMessage, cmdHeader.CMD_LEN, bMessage.Length - cmdHeader.CMD_LEN);
            return cmd;
        }

        /// <summary>
        /// 编码消息
        /// </summary>
        /// * 命令格式
        /// * 命令前导码 （1个byte） + 站点区域邮编（3个byte） + 设备编号（1个byte） + 读写标识（1个byte） + 命令长度（1个byte） + 命令地址（两个byte） + 数据域（命令长度） + CRC高字节（1个byte） + CRC低字节（1个byte）
        /// * 命令头有9个字节
        /// <param name="cmd">消息</param>
        /// <returns>序列化之后的字节数组</returns>
        public byte[] EncodeMessage(NetCommand cmd)
        {
            // 命令前导码
            byte cmd_front_id = cmd.Header.CMD_FRONT_ID;
            byte[] cmd_area_id = EncodingTools.shortToBytes( cmd.Header.CMD_AREA_ID);
            byte[] cmd_data = fillCommandData(cmd);
            byte cmd_size = (byte)(cmd_data.Length + NetCommandHeader.getSize() + NetCommon.CRCLength + NetCommon.SpareLength);

            if (cmd.Header.CMD_R_W == NetCommon.S2D_READ)
            {
                cmd_size = (byte)(cmd_data.Length + NetCommandHeader.getSize() + NetCommon.CRCLength);
            }
            byte[] cmd_id = EncodingTools.shortToBytes(cmd.Header.CMD_ID);
            byte[] spare = EncodingTools.shortToBytes(cmd.SpareByte);

            byte[] Header =
            {
                cmd_front_id,
                cmd_area_id[0],
                cmd_area_id[1],
                cmd.Header.CMD_DEVICE_ID,
                cmd.Header.CMD_R_W,
                cmd_size,
                cmd_id[0],
                cmd_id[1], // 命令地址
            };

            List<byte> bHD = new List<byte>(cmd_size);
            bHD.AddRange(Header);
            bHD.AddRange(cmd_data);

            if (cmd.Header.CMD_R_W != NetCommon.S2D_READ)
            {
                bHD.AddRange(new byte[] { 0, 0, 0, 0 });
                bHD[cmd_size - 4] = spare[0];
                bHD[cmd_size - 3] = spare[1];
                byte[] crc = EncodingTools.shortToBytes(CRC.crc16(EncodingTools.CopyBytes(bHD.ToArray(), bHD.Count - 2)));
                bHD[cmd_size - 2] = crc[0];
                bHD[cmd_size - 1] = crc[1];
            }
            else
            {
                bHD.AddRange(new byte[] { 0, 0});
                byte[] crc = EncodingTools.shortToBytes(CRC.crc16(EncodingTools.CopyBytes(bHD.ToArray(), bHD.Count - 2)));
                bHD[cmd_size - 2] = crc[0];
                bHD[cmd_size - 1] = crc[1];
            }
            return bHD.ToArray();
        }
    }
}
